/*
    Priscilla - A library for enocean network access
    Copyright (C) 2012-2013 B. Aigner / R. Wagner

    This program is free software; you can redistribute it and/or 
    modify it under the terms of the GNU General Public License 
    as published by the Free Software Foundation; either version 2 
    of the License, or at your option any later version. 
 
    This program is distributed in the hope that it will be useful, 
    but WITHOUT ANY WARRANTY; without even the implied warranty of 
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
    GNU General Public License for more details. 
 
    You should have received a copy of the GNU General Public License 
    along with this program; if not, write to the Free Software 
    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA. 
 
    Linking this library statically or dynamically with other modules is 
    making a combined work based on this library. Thus, the terms and 
    conditions of the GNU General Public License cover the whole 
    combination. 
 
    As a special exception, the copyright holders of this library give you 
    permission to link this library with independent modules to produce an 
    executable, regardless of the license terms of these independent 
    modules, and to copy and distribute the resulting executable under terms 
    of your choice, provided that you also meet, for each linked independent 
    module, the terms and conditions of the license of that module. An 
    independent module is a module which is not derived from or based on 
    this library. If you modify this library, you may extend this exception 
    to your version of the library, but you are not obligated to do so. If 
    you do not wish to do so, delete this exception statement from your 
    version. 
 */

package at.technikum_wien.embsys.aat.PriscillaCore.link;

import java.io.File;
import org.apache.log4j.Logger;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;

import at.technikum_wien.embsys.aat.PriscillaCore.connection.ISocketTCPConnection;
import at.technikum_wien.embsys.aat.PriscillaCore.connection.SerialConnector;
import at.technikum_wien.embsys.aat.PriscillaCore.connection.SocketConnector;
import at.technikum_wien.embsys.aat.PriscillaCore.enums.BinaryState;
import at.technikum_wien.embsys.aat.PriscillaCore.enums.ErrorCodeLRN;
import at.technikum_wien.embsys.aat.PriscillaCore.enums.ErrorCodeSend;
import at.technikum_wien.embsys.aat.PriscillaCore.enums.EventSend;
import at.technikum_wien.embsys.aat.PriscillaCore.enums.FanState;
import at.technikum_wien.embsys.aat.PriscillaCore.enums.FanType;
import at.technikum_wien.embsys.aat.PriscillaCore.enums.TeachInOptions;
import at.technikum_wien.embsys.aat.PriscillaCore.event.FrameEvent;
import at.technikum_wien.embsys.aat.PriscillaCore.event.FrameEventBinary;
import at.technikum_wien.embsys.aat.PriscillaCore.event.FrameEventError;
import at.technikum_wien.embsys.aat.PriscillaCore.event.FrameEventFan;
import at.technikum_wien.embsys.aat.PriscillaCore.event.FrameEventGetGatewayID;
import at.technikum_wien.embsys.aat.PriscillaCore.event.FrameEventHumidity;
import at.technikum_wien.embsys.aat.PriscillaCore.event.FrameEventIllumination;
import at.technikum_wien.embsys.aat.PriscillaCore.event.FrameEventLearnTelegram;
import at.technikum_wien.embsys.aat.PriscillaCore.event.FrameEventSend;
import at.technikum_wien.embsys.aat.PriscillaCore.event.FrameEventSetPoint;
import at.technikum_wien.embsys.aat.PriscillaCore.event.FrameEventSupplyVoltage;
import at.technikum_wien.embsys.aat.PriscillaCore.event.FrameEventTemperature;
import at.technikum_wien.embsys.aat.PriscillaCore.event.FrameEventWindowHandle;
import at.technikum_wien.embsys.aat.PriscillaCore.link.event.LinkListener;
import at.technikum_wien.embsys.aat.PriscillaCore.telegram.Device;
import at.technikum_wien.embsys.aat.PriscillaCore.telegram.Telegram;
import at.technikum_wien.embsys.aat.PriscillaCore.telegram.TelegramESP3;
import at.technikum_wien.embsys.aat.PriscillaCore.xml.DeviceListXML;

import gnu.io.CommPort;
import gnu.io.CommPortIdentifier;
import gnu.io.NoSuchPortException;
import gnu.io.PortInUseException;
import gnu.io.SerialPort;
import gnu.io.UnsupportedCommOperationException;


/**
 * Implementation of the EnoceanNetworkLink for the IP interface.
 * 
 * It implements the send functions, the gateway ID request (and the
 * corresponding response by adding a LinkListener).
 * 
 * @author R. Wagner
 * @author B. Aigner
 * 
 */
public class EnOceanLinkImpl implements EnOceanLink {

	private ISocketTCPConnection connIPGateway = null;
	
	private static final Logger log = Logger.getLogger(EnOceanLinkImpl.class);
	
	private SerialConnector connSerial = null;

	@SuppressWarnings("rawtypes")
	private List listeners = new ArrayList();
	@SuppressWarnings("unused")
	private InetSocketAddress remoteIP;
	private String gatewayDeviceID = "";

	private boolean SERIAL = false;

	private boolean IP_GATEWAY = false;
	
	private File devicesFile = new File(DeviceListXML.FILENAME_DEFAULT);

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * technikum.wien.aat.priscilla.link.EnOceanNetworkLink#addLinkListener(
	 * technikum.wien.aat.priscilla.link.event.LinkListener)
	 */
	@SuppressWarnings("unchecked")
	@Override
	public void addLinkListener(LinkListener l) {
		if (l == null)
			return;
		synchronized (listeners) {
			if (!listeners.contains(l)) {
				log.info("event listener registered");
				listeners.add(l);
			} else {
				log.warn("event listener already registered");
			}
		}
	}

	/**
	 * Open an IP link to the given IP address
	 * 
	 * @param remoteIP
	 *            IP address of the gateway
	 */
	public EnOceanLinkImpl(InetSocketAddress remoteIP) {
		super();
		IP_GATEWAY = true;
		SERIAL = false;

		this.remoteIP = remoteIP;

		try {
			this.connIPGateway = new SocketConnector(remoteIP, listeners);
		} catch (IOException e) {
			e.printStackTrace();
		}
		this.addListener();
	}

	/**
	 * Open an IP link to the given IP address, specify devices file
	 * 
	 * @param remoteIP
	 *            IP address of the gateway
	 * @param devicesFile
	 *            File to save device data
	 */
	public EnOceanLinkImpl(InetSocketAddress remoteIP, File devicesFile) {
		this(remoteIP);

		if (devicesFile != null) {
			this.devicesFile = devicesFile;
		}
	}

	/**
	 * Open an IP link to the given IP address, save the teach-in options
	 * 
	 * @param remoteIP
	 *            IP address of the gateway
	 * @param options
	 *            Options for the teach-in process
	 */
	public EnOceanLinkImpl(InetSocketAddress remoteIP, TeachInOptions options) throws IOException {
		this(remoteIP, options, SocketConnector.DEFAULT_TIMEOUT);
	}

	/**
	 * Open an IP link to the given IP address, save the teach-in options, set the timeout
	 * 
	 * @param remoteIP IP address of the gateway
	 * @param options Options for the teach-in process
	 * @param timeout Timeout for the socket connection in milliseconds
	 */
	public EnOceanLinkImpl(InetSocketAddress remoteIP, TeachInOptions options, Integer timeout) throws IOException {
		super();
		IP_GATEWAY = true;
		SERIAL = false;

		this.remoteIP = remoteIP;
		// logger = LogManager.getManager().getLogService(getName());
		try {
			// System.out.println("this.conn = new SocketConnector(remoteIP);");
			this.connIPGateway = new SocketConnector(remoteIP, listeners, options, timeout);
			log.info("IP connection established");
		} catch (IOException e) {
			log.error("IP IO exception");
			throw e;
		}
		this.addListener();
	}

	/**
	 * Open an IP link to the given IP address, save the teach-in options, specify the devices file
	 * 
	 * @param remoteIP IP address of the gateway
	 * @param options Options for the teach-in process
	 * @param timeout Timeout for the socket connection in milliseconds
	 * @param devicesFile File to save device data
	 */
	public EnOceanLinkImpl(InetSocketAddress remoteIP, TeachInOptions options, Integer timeout, File devicesFile) throws IOException {
		this(remoteIP, options, timeout);

		if (devicesFile != null) {
			this.devicesFile = devicesFile;
		}
	}

	/**
	 * Open an IP link to the given IP address, save the teach-in options, specify the devices file
	 * 
	 * @param remoteIP
	 *            IP address of the gateway
	 * @param options
	 *            Options for the teach-in process
	 * @param devicesFile
	 *            File to save device data
	 */
	public EnOceanLinkImpl(InetSocketAddress remoteIP, TeachInOptions options, File devicesFile) throws IOException {
		this(remoteIP, options);

		if (devicesFile != null) {
			this.devicesFile = devicesFile;
		}
	}

	/**
	 * Open an Serial link to the given serial port
	 * 
	 * @param String
	 *            portID id of the serial Port; e.g.: ttyUSB0
	 * @throws PortInUseException
	 * @throws UnsupportedCommOperationException
	 */
	public EnOceanLinkImpl(String serialPortID) {
		super();
		SERIAL = true;
		IP_GATEWAY = false;

		CommPortIdentifier portIdentifier = null;
		try {
			portIdentifier = CommPortIdentifier.getPortIdentifier(serialPortID);
			if (portIdentifier.isCurrentlyOwned()) {
				log.error("serial port is currently in use");
			} else {
				CommPort commPort;
				commPort = portIdentifier.open(this.getClass().getName(), 2000);
				if (commPort instanceof SerialPort) {
					SerialPort serialPort = (SerialPort) commPort;
					serialPort.setSerialPortParams(57600, SerialPort.DATABITS_8, SerialPort.STOPBITS_1, SerialPort.PARITY_NONE);
					this.connSerial = new SerialConnector(serialPort, listeners);
					log.info("serial connection established");
				}
			}
			this.addListener();
		} catch (NoSuchPortException e) {
			log.error("no such serial port for EnOcean link");
			e.printStackTrace();
		} catch (PortInUseException e) {
			log.error("serial port is in use");
			e.printStackTrace();
		} catch (UnsupportedCommOperationException e) {
			log.error("unsupported serial operation");
			e.printStackTrace();
		} catch (IOException e) {
			log.error("serial IO exception");
			e.printStackTrace();
		}

	}

	/**
	 * Open an Serial link to the given serial port
	 * 
	 * @param String
	 *            portID id of the serial Port; e.g.: ttyUSB0
	 * @param SerialPort
	 *            serialPortSettings settings for the serial interface
	 * 
	 * @throws PortInUseException
	 * @throws UnsupportedCommOperationException
	 */
	public EnOceanLinkImpl(String serialPortID, SerialPort serialPortSettings) {
		super();
		SERIAL = true;
		IP_GATEWAY = false;

		CommPortIdentifier portIdentifier = null;
		try {
			portIdentifier = CommPortIdentifier.getPortIdentifier(serialPortID);
			if (portIdentifier.isCurrentlyOwned()) {
				log.error("serial port is currently in use");
			} else {
				CommPort commPort = portIdentifier.open(this.getClass().getName(), 2000);

				if (commPort instanceof SerialPort) {
					SerialPort serialPort = (SerialPort) commPort;
					serialPort.setSerialPortParams(serialPortSettings.getBaudRate(), serialPortSettings.getDataBits(),
							serialPortSettings.getStopBits(), serialPortSettings.getParity());

					this.connSerial = new SerialConnector(serialPort, listeners);
					log.info("serial connection established");
				}
			}
			this.addListener();

		} catch (NoSuchPortException e) {
			log.error("no such serial port for EnOcean link");
			e.printStackTrace();
		} catch (PortInUseException e) {
			log.error("serial port is in use");
			e.printStackTrace();
		} catch (UnsupportedCommOperationException e) {
			log.error("unsupported serial operation");
			e.printStackTrace();
		} catch (IOException e) {
			log.error("serial IO exception");
			e.printStackTrace();
		}

	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * technikum.wien.aat.priscilla.link.EnOceanNetworkLink#sendTelegram(technikum
	 * .wien.aat.priscilla.telegram.Telegram)
	 */
	@Override
	public ErrorCodeSend sendTelegram(Telegram telegram) {
		if(IP_GATEWAY == true) {
			Socket socket = this.connIPGateway.getSocket(); // get socket
			
			if (socket == null) {
				log.error("sending without socket");
				return ErrorCodeSend.NO_SOCKET_CONNECTION_AVAILABLE;
			}
			try {
				OutputStreamWriter out = new OutputStreamWriter(socket.getOutputStream()); // open
																							// the
																							// socket
																							// for
																							// writing
				out.write(telegram.getIPTelegramString()); // write telegram string to the
														// socket
				out.flush(); // force sending it
				log.debug("sending telegram over IP");
			} catch (IOException e) {
				log.error("sending: IO exception");
				e.printStackTrace();
				return ErrorCodeSend.SEND_EXCEPTION;
			}
			return ErrorCodeSend.SUCCESS;
		}
		
		if(SERIAL == true) {
			
			TelegramESP3 telegramESP3 = new TelegramESP3(telegram);

			try {
				if(this.connSerial.getSender().send(telegramESP3)) {
					return ErrorCodeSend.SEND_EXCEPTION;
				}
			} catch (IOException e) {
				log.error("sending: IO exception");
				e.printStackTrace();
				return ErrorCodeSend.SEND_EXCEPTION;
			}
			return ErrorCodeSend.SUCCESS;
		}
		return ErrorCodeSend.NO_SOCKET_CONNECTION_AVAILABLE;
	}

	public ErrorCodeSend sendTelegram(String telegram) {
		if(IP_GATEWAY == true) {
			Socket socket = this.connIPGateway.getSocket(); // get socket
	
			if (socket == null) {
				return ErrorCodeSend.NO_SOCKET_CONNECTION_AVAILABLE;
			}
			try {
				OutputStreamWriter out = new OutputStreamWriter(socket.getOutputStream()); // open
																							// the
																							// socket
																							// for
																							// writing
				out.write(telegram); // write telegram string to the
														// socket
				out.flush(); // force sending it
				log.debug("sending telegram over IP");
			} catch (IOException e) {
				e.printStackTrace();
				log.error("sending: IO exception");
				return ErrorCodeSend.SEND_EXCEPTION;
			}
			return ErrorCodeSend.SUCCESS;
		}
		
		if(SERIAL == true) {

			try {
				if(this.connSerial.getSender().send(telegram) == false) {
					return ErrorCodeSend.SEND_EXCEPTION;
				}
			} catch (IOException e) {
				log.error("sending: IO exception");
				e.printStackTrace();
				return ErrorCodeSend.SEND_EXCEPTION;
			}
			return ErrorCodeSend.SUCCESS;
		}
		return ErrorCodeSend.NO_SOCKET_CONNECTION_AVAILABLE;
	}
	
	/**
	 * Request the gateway ID by sending a defined telegram
	 * 
	 * @return ErrorCodeSend from sending this telegram
	 */
	public ErrorCodeSend requestGatewayID() {
		
		log.debug("requesting gateway device ID");
		if(IP_GATEWAY == true) {
			return sendTelegram(new Telegram("A55AAB5800000000000000000003")); // send
																			// the
																			// request
																			// telegram
		}
		
		if (SERIAL == true) {
			return sendTelegram("5500010005700838"); //send the request telegram
		}
		
		return ErrorCodeSend.INCOMPATIBLE_EVENT_SENDER;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * technikum.wien.aat.priscilla.link.EnOceanNetworkLink#addDeviceManually
	 * (int, int, java.lang.String)
	 */
	@Override
	public ErrorCodeLRN addDeviceManually(int func, int type, String deviceID) {
		List<Device> list = new ArrayList<Device>();

		// parse device ID
		int devID3 = Integer.parseInt(deviceID.substring(0, 2), 16);
		int devID2 = Integer.parseInt(deviceID.substring(2, 4), 16);
		int devID1 = Integer.parseInt(deviceID.substring(4, 6), 16);
		int devID0 = Integer.parseInt(deviceID.substring(6, 8), 16);
		
		log.debug("adding device manually: " + deviceID + ", func: " + func + ", type: " + type);

		DeviceListXML deviceListXML = new DeviceListXML(devicesFile);
		// readout xml-data
		list = deviceListXML.getList();

		if (func == 0)
			return ErrorCodeLRN.FUNC_0_TELEGRAM;

		// search for device in the list
		for (int i = 0; i < list.size(); i++) {
			if (devID0 == list.get(i).id0) {
				if (devID1 == list.get(i).id1) {
					if (devID2 == list.get(i).id2) {
						if (devID3 == list.get(i).id3) {
							return ErrorCodeLRN.KNOWN_DEVICE;
						}
					}
				}
			}
		}

		// create the new device
		Device newdev = new Device();
		newdev.func = func;
		newdev.type = type;
		newdev.id0 = devID0;
		newdev.id1 = devID1;
		newdev.id2 = devID2;
		newdev.id3 = devID3;

		// add it to the list
		list.add(newdev);

		// recreate the XML file with the new list
		return deviceListXML.recreateXML(list);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * technikum.wien.aat.priscilla.link.EnOceanNetworkLink#modifiyDeviceManually
	 * (int, int, java.lang.String)
	 */
	@Override
	public ErrorCodeLRN modifiyDeviceManually(int func, int type, String deviceID) {
		List<Device> list = new ArrayList<Device>();

		// parse device ID
		int devID3 = Integer.parseInt(deviceID.substring(0, 2), 16);
		int devID2 = Integer.parseInt(deviceID.substring(2, 4), 16);
		int devID1 = Integer.parseInt(deviceID.substring(4, 6), 16);
		int devID0 = Integer.parseInt(deviceID.substring(6, 8), 16);
		
		log.debug("modifying device manually: " + deviceID + ", change to func: " + func + ", type: " + type);

		// create an old and a new device
		Device newdev = new Device();
		Device olddev = new Device();

		DeviceListXML deviceListXML = new DeviceListXML(devicesFile);
		// readout xml-data
		list = deviceListXML.getList();

		newdev.id0 = devID0;
		newdev.id1 = devID1;
		newdev.id2 = devID2;
		newdev.id3 = devID3;
		newdev.func = func;
		newdev.type = type;

		olddev.id0 = devID0;
		olddev.id1 = devID1;
		olddev.id2 = devID2;
		olddev.id3 = devID3;

		// search for device
		for (int i = 0; i < list.size(); i++) {
			if (devID0 == list.get(i).id0) {
				if (devID1 == list.get(i).id1) {
					if (devID2 == list.get(i).id2) {
						if (devID3 == list.get(i).id3) {
							olddev.type = list.get(i).type;
							olddev.func = list.get(i).func;
							list.set(i, newdev); // update list entry
						}
					}
				}
			}
		}

		// recreate device list with updated entry
		return deviceListXML.recreateXML(list);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * technikum.wien.aat.priscilla.link.EnOceanNetworkLink#removeDeviceManually
	 * (java.lang.String)
	 */
	@Override
	public ErrorCodeLRN removeDeviceManually(String deviceID) {
		List<Device> list = new ArrayList<Device>();

		// parse device ID
		int devID3 = Integer.parseInt(deviceID.substring(0, 2), 16);
		int devID2 = Integer.parseInt(deviceID.substring(2, 4), 16);
		int devID1 = Integer.parseInt(deviceID.substring(4, 6), 16);
		int devID0 = Integer.parseInt(deviceID.substring(6, 8), 16);
		
		log.debug("deleting device manually: " + deviceID);

		DeviceListXML deviceListXML = new DeviceListXML(devicesFile);
		// readout xml-data
		list = deviceListXML.getList();

		// search for device
		for (int i = 0; i < list.size(); i++) {
			if (devID0 == list.get(i).id0) {
				if (devID1 == list.get(i).id1) {
					if (devID2 == list.get(i).id2) {
						if (devID3 == list.get(i).id3) {
							list.remove(i); // remove the device
						}
					}
				}
			}
		}

		// recreate device list without this device
		return deviceListXML.recreateXML(list);
	}

	/**
	 * Send a event, not based on a device number.
	 * 
	 * Instead of a device number, a full device ID is used. The function is the
	 * same as the corresponding send function with the device number (as
	 * defined in the interface).
	 */
	public ErrorCodeSend sendEvent(EventSend event, String deviceID, float value) {
		FrameEventSend data = new FrameEventSend(event, deviceID, value);
		log.info("sending" + event + ", value: " + value);
		return sendTelegram(data.getTelegram());
	}

	/**
	 * Send a event, not based on a device number.
	 * 
	 * Instead of a device number, a full device ID is used. The function is the
	 * same as the corresponding send function with the device number (as
	 * defined in the interface).
	 */
	public ErrorCodeSend sendEvent(EventSend event, String deviceID, BinaryState channelA, BinaryState channelB, BinaryState channelC,
			BinaryState channelD) {
		FrameEventSend data = new FrameEventSend(event, deviceID, channelA, channelB, channelC, channelD);
		log.info("sending" + event + ", binary values: " + channelA + " " + channelB + " " + channelC + " " + channelD + " ");
		return sendTelegram(data.getTelegram());
	}

	/**
	 * Send a event, not based on a device number.
	 * 
	 * Instead of a device number, a full device ID is used. The function is the
	 * same as the corresponding send function with the device number (as
	 * defined in the interface).
	 */
	public ErrorCodeSend sendEvent(EventSend event, String deviceID, FanState fanstate, FanType fantype) {
		FrameEventSend data = new FrameEventSend(event, deviceID, fanstate, fantype);
		log.info("sending" + event + ", fanstate: " + fanstate + ", type: " + fantype);
		return sendTelegram(data.getTelegram());
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * technikum.wien.aat.priscilla.link.EnOceanNetworkLink#sendEvent(technikum
	 * .wien.aat.priscilla.enums.EventSend, int, float)
	 */
	@Override
	public ErrorCodeSend sendEvent(EventSend event, int deviceNr, float value) {
		int deviceID;
		ErrorCodeSend ret;

		ret = preParse(deviceNr); // pre parse, check for valid input

		if (ret != ErrorCodeSend.SUCCESS) {
			return ret;
		}

		deviceID = Integer.parseInt(gatewayDeviceID.substring(6, 8), 16) + deviceNr; // create
																						// a
																						// full
																						// device
																						// ID
		// create a new FrameEventSend
		FrameEventSend data = new FrameEventSend(event, gatewayDeviceID.substring(0, 6) + Integer.toString(deviceID, 16).toUpperCase(), value);
		
		log.info("sending" + event + ", value: " + value);
		return sendTelegram(data.getTelegram()); // send the data
	}

	@Override
	public ErrorCodeSend sendEvent(EventSend event, int deviceNr, BinaryState channelA, BinaryState channelB, BinaryState channelC,
			BinaryState channelD) {
		int deviceID;
		ErrorCodeSend ret;

		ret = preParse(deviceNr); // pre parse, check for valid input

		if (ret != ErrorCodeSend.SUCCESS) {
			System.err.println("No success while preparsing...");
			return ret;
		}

		deviceID = Integer.parseInt(gatewayDeviceID.substring(6, 8), 16) + deviceNr;
		FrameEventSend data = new FrameEventSend(event, gatewayDeviceID.substring(0, 6) + Integer.toString(deviceID, 16).toUpperCase(), channelA,
				channelB, channelC, channelD);

		log.info("sending" + event + ", binary values: " + channelA + " " + channelB + " " + channelC + " " + channelD + " ");
		return sendTelegram(data.getTelegram());
	}

	@Override
	public ErrorCodeSend sendEvent(EventSend event, int deviceNr, FanState fanstate, FanType fantype) {
		int deviceID;
		ErrorCodeSend ret;

		ret = preParse(deviceNr); // pre parse, check for valid input

		if (ret != ErrorCodeSend.SUCCESS) {
			return ret;
		}

		deviceID = Integer.parseInt(gatewayDeviceID.substring(6, 8), 16) + deviceNr;
		FrameEventSend data = new FrameEventSend(event, gatewayDeviceID.substring(0, 6) + Integer.toString(deviceID, 16).toUpperCase(), fanstate,
				fantype);
		log.info("sending" + event + ", fanstate: " + fanstate + ", type: " + fantype);
		return sendTelegram(data.getTelegram());
	}

	/**
	 * Check the input data and gateway device ID
	 * 
	 * This method checks the validity of the input device number. Additional,
	 * if the gateway ID was not requested before, the method sends a telegram
	 * to receive gateway address.
	 * 
	 * @param deviceNr
	 *            Number of device address (valid: 0-127)
	 * @return ErrorCodeSend
	 */
	private ErrorCodeSend preParse(int deviceNr) {
		ErrorCodeSend ret;
		long timems = System.currentTimeMillis();
		boolean condition = true;

		if (deviceNr > 127) { // check for valid device number
			return ErrorCodeSend.DEVICE_NR_OUT_OF_RANGE;
		}

		// is the gatewayID empty?
		if (gatewayDeviceID.equals("")) {
			ret = this.requestGatewayID(); // request a new gateway ID
			if (ret != ErrorCodeSend.SUCCESS) {
				return ret;
			}
			while (condition) // wait for the response from the gateway
			{
				if (!gatewayDeviceID.equals("")) {
					condition = false;
				}
				try {
					Thread.sleep(10);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				// timeout: if there is no response in 5s, exit with timeout
				// error
				if ((System.currentTimeMillis() - timems) > 5000) {
					return ErrorCodeSend.TIMEOUT;
				}
			}
		}

		return ErrorCodeSend.SUCCESS;
	}

	/**
	 * Add a LinkListener, to subscribe to the frameReceived_GatewayID event.
	 * This event is fired, if the gateway sends the response to a gateway ID
	 * request with the actual gateway ID. It will be saved for sending events.
	 */
	private void addListener() {
		this.addLinkListener(new LinkListener() {
			@Override
			public void frameReceived_Binary(FrameEventBinary e) {
			}

			@Override
			public void frameReceived_Temperature(FrameEventTemperature e) {
			}

			@Override
			public void frameReceived_Humidity(FrameEventHumidity e) {
			}

			@Override
			public void frameReceived_Illumination(FrameEventIllumination e) {
			}

			@Override
			public void frameReceived_SupplyVoltage(FrameEventSupplyVoltage e) {
			}

			@Override
			public void frameReceived_SetPoint(FrameEventSetPoint e) {
			}

			@Override
			public void frameReceived_Fan(FrameEventFan e) {
			}

			@Override
			public void frameReceived_DayNight(FrameEvent e) {
			}

			@Override
			public void frameReceived_Gas(FrameEvent e) {
			}

			@Override
			public void frameReceived_RainSensor(FrameEvent e) {
			}

			@Override
			public void frameReceived_Time(FrameEvent e) {
			}

			@Override
			public void frameReceived_WeatherStation(FrameEvent e) {
			}

			@Override
			public void frameReceived_GatewayID(FrameEventGetGatewayID e) {
				log.debug("gateway ID received: " + e.getGatewayID());
				gatewayDeviceID = e.getGatewayID();
			}

			@Override
			public void frameReceived_LearnTelegram(FrameEventLearnTelegram e) {
			}

			@Override
			public void frameReceived_Error(FrameEventError e) {
			}

			public void frameReceived(FrameEvent e) {
			}

			@Override
			public void frameReceived_WindowHandle(FrameEventWindowHandle e) {

			}
		});

	}
}
